package com.zzq.demo.excel;

import com.alibaba.excel.EasyExcelFactory;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.metadata.BaseRowModel;
import com.alibaba.excel.metadata.Sheet;
import com.alibaba.excel.support.ExcelTypeEnum;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.List;

/**
 * ExcelUtil
 * 基于easyExcel的开源框架，poi版本3.17
 * BeanCopy ExcelException 属于自定义数据，属于可自定义依赖
 * 工具类尽可能还是需要减少对其他java的包的依赖
 */
public class ExcelUtil {
    /**
     * 私有化构造方法
     */
    private ExcelUtil() {
    }

    /**
     * 读取 Excel(多个 sheet) 要求多个sheet是一个格式
     * 将多sheet合并成一个list数据集，通过自定义ExcelReader继承 {@link BaseExcelListener}
     * 重写invoke doAfterAllAnalysed方法
     * getExtendsBeanList 主要是做Bean的属性拷贝 ，可以通过ExcelReader中添加的数据集直接获取
     *
     * @param excel         文件
     * @param excelListener 自定义ExcelListener T 继承 {@link BaseRowModel}
     * @return Excel 数据 list
     */
    public static <T extends BaseRowModel> List<T> readExcel(MultipartFile excel, BaseExcelListener<T> excelListener) throws ExcelException {
        ExcelReader reader = getReader(excel, excelListener);
        for (Sheet sheet : reader.getSheets()) {
            sheet.setClazz(excelListener.getClazz());
            reader.read(sheet);
        }
        return excelListener.getDataList();
    }

    /**
     * 读取某个 sheet 的 Excel
     *
     * @param excel         文件
     * @param excelListener 自定义ExcelListener T 继承 {@link BaseRowModel}
     * @param sheetNo       sheet 的序号 从1开始
     * @return Excel 数据 list
     */
    public static <T extends BaseRowModel> List<T> readExcel(MultipartFile excel, BaseExcelListener<T> excelListener, int sheetNo) throws ExcelException {
        return readExcel(excel, excelListener, sheetNo, 1);
    }

    /**
     * 读取某个 sheet 的 Excel
     *
     * @param excel         文件
     * @param excelListener 自定义ExcelListener T 继承 {@link BaseRowModel}
     * @param sheetNo       sheet 的序号 从1开始
     * @param headLineNum   表头行数，默认为1
     * @return Excel 数据 list
     */
    public static <T extends BaseRowModel> List<T> readExcel(MultipartFile excel, BaseExcelListener<T> excelListener, int sheetNo, int headLineNum) throws ExcelException {
        ExcelReader reader = getReader(excel, excelListener);
        reader.read(new Sheet(sheetNo, headLineNum, excelListener.getClazz()));
        return excelListener.getDataList();
    }

    /**
     * 读取某个 sheet 的 Excel
     *
     * @param excel         文件
     * @param excelListener 自定义ExcelListener T 继承 {@link BaseRowModel}
     * @param sheetNo       sheet 的序号 从1开始
     * @return Excel 数据 list
     */
    public static <T extends BaseRowModel> List<T> readExcel(File excel, BaseExcelListener<T> excelListener, int sheetNo) throws ExcelException {
        return readExcel(excel, excelListener, sheetNo, 1);
    }

    /**
     * 读取某个 sheet 的 Excel
     *
     * @param excel         文件
     * @param excelListener 自定义ExcelListener T 继承 {@link BaseRowModel}
     * @param sheetNo       sheet 的序号 从1开始
     * @param headLineNum   表头行数，默认为1
     * @return Excel 数据 list
     */
    public static <T extends BaseRowModel> List<T> readExcel(File excel, BaseExcelListener<T> excelListener, int sheetNo, int headLineNum) throws ExcelException {
        ExcelReader reader = getReader(excel, excelListener);
        reader.read(new Sheet(sheetNo, headLineNum, excelListener.getClazz()));
        return excelListener.getDataList();
    }

    /**
     * 导出 Excel ：一个 sheet，带表头
     *
     * @param response      HttpServletResponse
     * @param list          数据 list，每个元素为一个 BaseRowModel
     * @param fileName      导出的文件名
     * @param sheetName     导入文件的 sheet 名
     * @param classType     写出Excel的对象模型 继承 {@link BaseRowModel}
     * @param excelTypeEnum Excel 版本 {@link ExcelTypeEnum}
     */
    public static <T extends BaseRowModel> void writeExcel(HttpServletResponse response, List<T> list, String fileName, String sheetName, Class<T> classType, ExcelTypeEnum excelTypeEnum) throws ExcelException {
        if (MyStringUtils.isBlank(fileName)) {
            throw new IllegalArgumentException("文件名不能为空");
        }
        if (sheetName == null || "".equals(sheetName)) {
            sheetName = "sheet1";
        }
        ExcelWriterFactory writer = new ExcelWriterFactory(getOutputStream(fileName, response, excelTypeEnum), excelTypeEnum);
        Sheet sheet = new Sheet(1, 0, classType);
        sheet.setSheetName(sheetName);
        try {
            writer.write(list, sheet);
        } finally {
            writer.finish();
            writer.close();
        }
    }

    /**
     * 导出 Excel ：一个 sheet，带表头
     *
     * @param list          数据 list，每个元素为一个 BaseRowModel
     * @param fileName      导出的文件名
     * @param sheetName     导入文件的 sheet 名
     * @param classType     写出Excel的对象模型 继承 {@link BaseRowModel}
     * @param excelTypeEnum Excel 版本 {@link ExcelTypeEnum}
     */
    public static <T extends BaseRowModel> void writeExcel(List<T> list, String fileName, String sheetName, Class<T> classType, ExcelTypeEnum excelTypeEnum) throws FileNotFoundException {
        if (MyStringUtils.isBlank(fileName)) {
            throw new IllegalArgumentException("文件名不能为空");
        }
        if (sheetName == null || "".equals(sheetName)) {
            sheetName = "sheet1";
        }
        ExcelWriterFactory writer = null;
        try {
            OutputStream outputStream = new FileOutputStream(fileName);
            writer = new ExcelWriterFactory(outputStream, excelTypeEnum);
            Sheet sheet = new Sheet(1, 1, classType, sheetName, null);
            sheet.setAutoWidth(true);
            writer.write(list, sheet);
        } finally {
            writer.finish();
            writer.close();
        }
    }

    /**
     * 导出 Excel ：多个 sheet，带表头
     *
     * @param fileName      导出的文件名
     * @param excelTypeEnum Excel 版本 {@link ExcelTypeEnum}
     * @param data          Excel每个Sheet的对象， sheet名称重复会导致导出excel中sheet被覆盖
     */
    public static void writeExcelWithSheets(String fileName, ExcelTypeEnum excelTypeEnum, MultiSheetExcel... data) throws FileNotFoundException, ExcelException {
        if (MyStringUtils.isBlank(fileName)) {
            throw new IllegalArgumentException("文件名不能为空");
        }
        ExcelWriterFactory writer = null;
        try {
            OutputStream outputStream = new FileOutputStream(fileName);
            writer = new ExcelWriterFactory(outputStream, excelTypeEnum);
            if (data == null || data.length == 0) {
                throw new ExcelException("导出数据");
            }

            for (MultiSheetExcel datum : data) {
                writer.write(datum);
            }
        } finally {
            writer.finish();
            writer.close();
        }
    }

    /**
     * 导出 Excel ：多个 sheet，带表头
     *
     * @param response      HttpServletResponse
     * @param fileName      导出的文件名
     * @param excelTypeEnum Excel 版本 {@link ExcelTypeEnum}
     * @param data          Excel每个Sheet的对象， sheet名称重复会导致导出excel中sheet被覆盖
     */
    public static void writeExcelWithSheets(HttpServletResponse response, String fileName, ExcelTypeEnum
            excelTypeEnum, MultiSheetExcel... data) throws ExcelException {
        ExcelWriterFactory writer = new ExcelWriterFactory(getOutputStream(fileName, response, excelTypeEnum), excelTypeEnum);
        if (data == null || data.length == 0) {
            throw new ExcelException("导出数据");
        }
        try {
            for (MultiSheetExcel datum : data) {
                writer.write(datum);
            }
        } finally {
            writer.finish();
            writer.close();
        }
    }

    /**
     * 导出文件时为Writer生成OutputStream
     */
    private static OutputStream getOutputStream(String fileName, HttpServletResponse response, ExcelTypeEnum
            excelTypeEnum) throws ExcelException {
        //创建本地文件
        String filePath = fileName + excelTypeEnum.getValue();
        try {
            fileName = new String(filePath.getBytes(), StandardCharsets.ISO_8859_1);
            response.addHeader("Content-Disposition", "filename=" + fileName);
            return response.getOutputStream();
        } catch (IOException e) {
            throw new ExcelException("创建文件失败！");
        }
    }

    /**
     * 返回 ExcelReader
     *
     * @param excel         需要解析的 Excel 文件
     * @param excelListener new ExcelListener()
     */
    private static ExcelReader getReader(MultipartFile excel,
                                         BaseExcelListener<?> excelListener) throws ExcelException {
        checkFileName(excel.getOriginalFilename());
        InputStream inputStream;
        try {
            inputStream = new BufferedInputStream(excel.getInputStream());
            return new ExcelReader(inputStream, null, excelListener, false);
        } catch (IOException e) {
            throw new ExcelException("文件读取错误！");
        }
    }

    public static ExcelReader getReader(File file, BaseExcelListener<?> excelListener) throws ExcelException {
        checkFileName(file.getName());
        try {
            FileInputStream fileInputStream = new FileInputStream(file);
            return reader(fileInputStream, excelListener);
        } catch (FileNotFoundException e) {
            throw new ExcelException("文件读取错误！");
        }
    }

    private static ExcelReader reader(InputStream in, BaseExcelListener<?> excelListener) {
        return new ExcelReader(new BufferedInputStream(in), null, excelListener, false);
    }

    private static void checkFileName(String fileName) throws ExcelException {
        if (fileName == null) {
            throw new ExcelException("文件格式错误！");
        }
        if (!fileName.toLowerCase().endsWith(ExcelTypeEnum.XLS.getValue()) && !fileName.toLowerCase().endsWith(ExcelTypeEnum.XLSX.getValue())) {
            throw new ExcelException("文件格式错误！");
        }
    }
}

